Expand description
This crate implements generators for Rust. Generators are a feature common across many programming language. They let you yield a sequence of values from a function. A few common use cases are:
- Easily building iterators.
- Avoiding allocating a list for a function which returns multiple values.
Rust has this feature too, but it is currently unstable (and thus nightly-only). But with this crate, you can use them on stable Rust!
§Features
This crate has these features:
futures03
(disabled by default) – ImplementsStream
for all generator types. Adds a dependency onfutures-core
.proc_macro
(enabled by default) – Adds support for macros, and adds various compile-time dependencies.
§Choose your guarantees
This crate supplies three concrete implementations of generators:
-
genawaiter::stack
– Allocation-free. You should prefer this when possible. -
genawaiter::rc
– This allocates. -
genawaiter::sync
– This allocates, and can be shared between threads.
Here are the differences in table form:
stack::Gen | rc::Gen | sync::Gen | |
---|---|---|---|
Allocations per generator | 0 | 2 | 2 |
Generator can be moved after created | no | yes | yes |
Thread-safe | no | no | yes |
§Creating a generator
Once you’ve chosen how and whether to allocate (see previous section), you can create a
generator using a macro from the gen
family:
let count_to_ten = gen!({
for n in 0..10 {
yield_!(n);
}
});
To re-use logic between multiple generators, you can use a macro from the producer
family, and then pass the producer to Gen::new
.
- [
stack_producer!
] andlet_gen_using!
rc_producer!
andGen::new
sync_producer!
andGen::new
let count_producer = producer!({
for n in 0..10 {
yield_!(n);
}
});
let count_to_ten = Gen::new(count_producer);
If neither of these offers enough control for you, you can always skip the macros and use the low-level API directly:
let count_to_ten = Gen::new(|co| async move {
for n in 0..10 {
co.yield_(n).await;
}
});
§A tale of three types
A generator can control the flow of up to three types of data:
- Yield – Each time a generator suspends execution, it can produce a value.
- Resume – Each time a generator is resumed, a value can be passed in.
- Completion – When a generator completes, it can produce one final value.
§Yield
Values can be yielded from the generator by calling yield_
, and immediately awaiting
the future it returns. You can get these values out of the generator in either of two
ways:
-
Call
resume()
orresume_with()
. The values will be returned in aGeneratorState::Yielded
.let mut generator = gen!({ yield_!(10); }); let ten = generator.resume(); assert_eq!(ten, GeneratorState::Yielded(10));
-
Treat it as an iterator. For this to work, both the resume and completion types must be
()
.let generator = gen!({ yield_!(10); }); let xs: Vec<_> = generator.into_iter().collect(); assert_eq!(xs, [10]);
§Resume
You can also send values back into the generator, by using resume_with
. The generator
receives them from the future returned by yield_
.
let mut printer = gen!({
loop {
let string = yield_!(());
println!("{}", string);
}
});
printer.resume_with("hello");
printer.resume_with("world");
§Completion
A generator can produce one final value upon completion, by returning it from the
function. The consumer will receive this value as a GeneratorState::Complete
.
let mut generator = gen!({
yield_!(10);
"done"
});
assert_eq!(generator.resume(), GeneratorState::Yielded(10));
assert_eq!(generator.resume(), GeneratorState::Complete("done"));
§Async generators
If you await other futures inside the generator, it becomes an async generator. It
does not makes sense to treat an async generator as an Iterable
, since you cannot
await
an Iterable
. Instead, you can treat it as a Stream
. This requires opting in
to the dependency on futures
with the futures03
feature.
[dependencies]
genawaiter = { version = "...", features = ["futures03"] }
async fn async_one() -> i32 { 1 }
async fn async_two() -> i32 { 2 }
let gen = gen!({
let one = async_one().await;
yield_!(one);
let two = async_two().await;
yield_!(two);
});
let stream = block_on_stream(gen);
let items: Vec<_> = stream.collect();
assert_eq!(items, [1, 2]);
Async generators also provide a async_resume
method for lower-level control. (This
works even without the futures03
feature.)
match gen.async_resume().await {
GeneratorState::Yielded(_) => {}
GeneratorState::Complete(_) => {}
}
§Backported stdlib types
This crate supplies Generator
and
GeneratorState
. They are copy/pasted from the stdlib (with
stability attributes removed) so they can be used on stable Rust. If/when real
generators are stabilized, hopefully they would be drop-in replacements. Javascript
developers might recognize this as a polyfill.
There is also a Coroutine
trait, which does not come from the
stdlib. A Coroutine
is a generalization of a Generator
. A Generator
constrains the
resume argument type to ()
, but in a Coroutine
it can be anything.
Modules§
- This module implements a generator which stores its state on the heap.
- This module implements a generator which doesn’t allocate.
- This module implements a generator which can be shared between threads.
Macros§
- generator_
mut Deprecated Creates a generator on the stack. - Creates a producer for use with
rc::Gen
. - Creates a producer for use with
sync::Gen
. - unsafe_
create_ generator Deprecated Creates a generator on the stack unsafely. - Yields a value from a generator.
Enums§
- The result of a generator resumption.
Traits§
- A trait implemented for coroutines.
- A trait implemented for generator types.